#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include "stringUtil.h"
#include "pipeUtil.h"


#define MAX_CMD_LINE_LENGTH 100
#define MAX_CMD_NUMBER 4
#define MAX_CMD_LENGTH 32
#define MAX_ARG_LENGTH 100

void cmdParser(char *cmd);
void cmd_scheduler(char cmds[MAX_CMD_NUMBER][MAX_CMD_LENGTH], int cmd_num, char *output_file);
// Declare function prototype
void redirect_inout(int i, int cmd_num, int (*pipefd)[2], int output_fd);


// Declare function prototypes
char *trim(char *str);
char *myfgets(char *str, int len);


int main() {
	char cmd[MAX_CMD_LINE_LENGTH]; // The variable is used to hold the user input.
	// Use a while loop to repeat the reading.
	while (1) {
		printf("MyShPrompt>");
		myfgets(cmd, MAX_CMD_LINE_LENGTH);
		if (strcmp(cmd, "exit") == 0)
			break;
		// printf("cmd: %s, length: %lu\n", cmd, strlen(cmd));
		cmdParser(cmd);
	}
}

void cmdParser(char *cmd_line_str) {
	int cmd_num = 0;
	char cmds[MAX_CMD_NUMBER][MAX_CMD_LENGTH];
	char *output_file;
	char *cmds_str;
	const char cmd_line_delimiters[] = ">";
	const char cmd_delimiters[] = "|";

	// Tokenize output file and commands
	cmds_str = trim(strtok(cmd_line_str, cmd_line_delimiters));
	output_file = trim(strtok(NULL, cmd_line_delimiters));
	// Tokenize commands
	char *cmd_str = trim(strtok(cmds_str, cmd_delimiters));
	while (cmd_str != NULL) {
		strcpy(cmds[cmd_num++], cmd_str);
		cmd_str = trim(strtok(NULL, cmd_delimiters));
	}
	cmd_scheduler(cmds, cmd_num, output_file);
}

void cmd_scheduler(char cmds[MAX_CMD_NUMBER][MAX_CMD_LENGTH], int cmd_num, char *output_file) {
	// Create a table for pipe ends.
	int(*pipefd)[2] = malloc((cmd_num - 1) * sizeof(*pipefd));
	if (pipefd == NULL) {
		perror("malloc");
		exit(EXIT_FAILURE);
	}
	// Create a table for process ids
	pid_t pid[4];
	// Open the outputfile.
	int output_fd = output_file == NULL ? STDOUT_FILENO : open(output_file, O_WRONLY | O_CREAT, 0644);
	if (output_fd == -1) {
		perror("open");
		exit(1);
	}

	// Use a for loop to run commands.
	for (int i = 0; i < cmd_num; i++) {
		// Create pipes
		if (i < cmd_num - 1) {
			if (pipe(pipefd[i]) == -1) {
				perror("pipe");
				exit(EXIT_FAILURE);
			}
		}

		// Get cmd and cmd args.
		char *args[MAX_ARG_LENGTH];
		int arg_num;
		char *cmd;
		const char *arg_delimiters = " ";
		cmd = strtok(cmds[i], arg_delimiters);
		args[0] = cmd;
		arg_num = 1;
		char *arg = strtok(NULL, arg_delimiters);
		while (arg != NULL) {
			args[arg_num] = arg;
			arg_num += 1;
			arg = strtok(NULL, arg_delimiters);
		}
		args[arg_num] = NULL;

		// Fork child process and execute commands.
		pid[i] = fork();
		if (pid[i] < 0) {
			perror("fork");
			exit(EXIT_FAILURE);
		} else if (pid[i] == 0) {
			// Redirect input and output.
			redirect_inout(i, cmd_num, pipefd, output_fd);
			// Execute the command.
			execvp(cmd, args);
			perror("execvp");
			exit(EXIT_FAILURE);
		}
	}

	// Close pipe ends in the main process.
	for (int i = 0; i < cmd_num - 1; i++) {
		close(pipefd[i][0]);
		close(pipefd[i][1]);
	}

	// Wait all child processes to terminate.
	for (int i = 0; i < cmd_num; i++) {
		int status;
		waitpid(pid[i], &status, 0);
	}

	// Free the allocated memory and exit the main process.
	free(pipefd);
}





char *trim(char *str) { //stringUtil.c
	// Check for empty string
	if (str == NULL) {
		return NULL;
	}
	if (*str == '\0') {
		return str;
	}

	// Remove leading whitespace
	while (isspace(*str)) {
		str++;
	}

	// Remove trailing whitespace
	char *end = str + strlen(str) - 1;
	while (end > str && isspace(*end)) {
		end--;
	}
	*(end + 1) = '\0';

	return str;
}

char *myfgets(char *str, int len) {
	fgets(str, len, stdin);
	*(str + strlen(str) - 1) = '\0';
}





void redirect_inout(int i, int cmd_num, int (*pipefd)[2], int output_fd) { //pipeUtil.c
	if (i == cmd_num - 1) {
		dup2(pipefd[i - 1][0], STDIN_FILENO);
		dup2(output_fd, STDOUT_FILENO);
		for (int j = 0; j < i; j++) {
			close(pipefd[j][0]);
			close(pipefd[j][1]);
		}
	} else if (i == 0) {
		dup2(pipefd[i][1], STDOUT_FILENO);
		close(pipefd[i][0]);
		close(pipefd[i][1]);
	} else {
		dup2(pipefd[i - 1][0], STDIN_FILENO);
		dup2(pipefd[i][1], STDOUT_FILENO);
		for (int j = 0; j < i; j++) {
			close(pipefd[j][0]);
			close(pipefd[j][1]);
		}
	}
}
